Skip to content

Dangling Symlink Resolution and Missing sync_file_range and syncfs System Calls#110

Merged
jserv merged 1 commit into
sysprog21:mainfrom
open-sources-port:sync_file_range
Jun 30, 2026
Merged

Dangling Symlink Resolution and Missing sync_file_range and syncfs System Calls#110
jserv merged 1 commit into
sysprog21:mainfrom
open-sources-port:sync_file_range

Conversation

@doanbaotrung

@doanbaotrung doanbaotrung commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

the sys_renameat2 and sys_linkat system calls
were using the resolving PATH_TR_NONE translation
mode for the source paths.
This caused elfuse to follow the symlink
leaf names to their targets, causing
dangling symlinks during package extraction
to fail with ENOENT (No such file or directory).
We updated these to use PATH_TR_NOFOLLOW for
the leaf components, preventing path resolution
from walking past the symlink itself.

Missing sync_file_range and syncfs System
Calls: We registered and defined
SYS_sync_file_range (84) and
SYS_syncfs (267) in abi.h and dispatch.tbl,
and implemented them in syscall.c as
aliases of sc_fsync_common.


Summary by cubic

Fixes dangling symlink handling in renameat2/linkat to prevent ENOENT during package extraction. Adds SYS_sync_file_range (84) and SYS_syncfs (267) with minimal Linux-compatible behavior and smoke tests.

  • Bug Fixes

    • renameat2 and linkat stop following leaf symlinks (PATH_TR_NOFOLLOW); linkat honors AT_SYMLINK_FOLLOW for the source. New targets use PATH_TR_CREATE | PATH_TR_NOFOLLOW.
  • New Features

    • Registered and implemented SYS_sync_file_range/SYS_syncfs. sync_file_range validates fd/offset/len, allows only flag bits 1/2/4, returns immediately for WRITE-only, calls fsync(fd) when WAIT flags are set, and returns ESPIPE on non-regular fds (e.g., pipes). syncfs validates the fd then calls sync(). Added smoke tests covering success and errors (EINVAL, EBADF, ESPIPE).

Written for commit bc965a3. Summary will update on new commits.

Review in cubic

cubic-dev-ai[bot]

This comment was marked as resolved.

@Max042004 Max042004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add smoke test for two new syscall.

Comment thread src/syscall/syscall.c
*/
int64_t ret = 0;
if (flags & (1u | 4u)) {
ret = (fsync(host_ref.fd) < 0) ? linux_errno() : 0;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sync_file_range(2) on native Linux don't write back metadata. But MacOS don't provide syscall to accomplish such feature. fsync() will write back metadata.
So please add comment for mentioning different behavior comparing to Linux sync_file_range(2)

Comment thread src/syscall/syscall.c
return err;

/* Validate flags: only bits 1, 2, 4 are valid */
if (flags & ~7u) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add offset, nbytes invalid check

if (offset < 0 || nbytes < 0 || offset + nbytes < offset)
    return -LINUX_EINVAL;

@doanbaotrung doanbaotrung force-pushed the sync_file_range branch 3 times, most recently from dd617b3 to c68c7d1 Compare June 30, 2026 05:55
@doanbaotrung doanbaotrung requested a review from Max042004 June 30, 2026 05:56
Comment thread src/syscall/syscall.c
return -LINUX_EINVAL;

host_fd_ref_t host_ref;
int64_t err = host_fd_ref_open_io(fd, &host_ref);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainline Linux rejects sync_file_range() on a descriptor that is not a
regular file, block device, or directory, returning -ESPIPE.
The current shim does not
classify the fd, so calling it on a pipe / socket / fifo / char device. Linux returns -ESPIPE in both cases.

Suggested fix (after host_fd_ref_open_io succeeds):

struct stat st;
if (fstat(host_ref.fd, &st) == 0 &&
    !S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode) && !S_ISDIR(st.st_mode)) {
    host_fd_ref_close(&host_ref);
    return -LINUX_ESPIPE;
}

Comment thread src/syscall/dispatch.tbl Outdated
SYS_fsync sc_fsync 1
SYS_fdatasync sc_fdatasync 1
SYS_sync_file_range sc_sync_file_range 1
SYS_syncfs sc_syncfs 1

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scripts/gen-syscall-dispatch.py:141-142:

 * E=0 means only x0-x2 are required by the wrapper.
 * E=1 means x3-x5 may also be consumed by the wrapper.

sc_syncfs (src/syscall/syscall.c:1294) only uses x0 and explicitly voids
the rest.

Suggested fix:

SYS_syncfs sc_syncfs 0

@Max042004

Copy link
Copy Markdown
Collaborator

The added tests pass (test-syscall-smoke: 21 passed, 0 failed under elfuse)
and scripts/check-syscall-coverage.py reports PASS — both syscalls are
recognized as covered. At the branch level, however, test_sync_file_range
only ever passes flags values of 7 and 8, so two branches of
sc_sync_file_range are never hit:

Branch in src/syscall/syscall.c Exercised? By
offset/nbytes/overflow → -EINVAL (1258) yes neg-offset / neg-nbytes / overflow cases
host_fd_ref_open_io → -EBADF (1262) no
flags & ~7u → -EINVAL (1267) yes flags = 8 case
flags & (1|4) truefsync() (1286) yes flags = 7 case
flags & (1|4) falsereturn 0 (1285) no

The untested return 0 branch is the most notable gap: it is the
SYNC_FILE_RANGE_WRITE-only (async-hint) path, and it is exactly the branch
where this shim intentionally diverges from Linux (returns success without
initiating any write-out). It currently has zero coverage. The -EBADF path for
sync_file_range is also untested (syncfs does test its -EBADF).

Both gaps close with two extra cases that also pass on native Linux (so no
qemu-mode divergence):

/* SYNC_FILE_RANGE_WRITE only (async hint) returns 0 without blocking.
 * Covers the deliberate-divergence branch. */
if (syscall(SYS_sync_file_range, fd, (off64_t) 0, (off64_t) 0, 2) != 0) {
    FAIL("sync_file_range write-only");
    close(fd);
    return;
}

/* Bad fd → EBADF. */
errno = 0;
if (syscall(SYS_sync_file_range, -1, (off64_t) 0, (off64_t) 0, 7) != -1 ||
    errno != EBADF) {
    FAIL("sync_file_range bad fd");
    close(fd);
    return;
}

test_syncfs is fine — both of its branches (success and -EBADF) are covered.

In fs.c, the sys_renameat2 and sys_linkat system
calls were using the resolving PATH_TR_NONE translation
mode for the source paths.

This caused elfuse to follow the symlink
leaf names to their targets, causing
dangling symlinks during package extraction
to fail with ENOENT (No such file or directory).
We updated these to use PATH_TR_NOFOLLOW for
the leaf components, preventing path resolution
from walking past the symlink itself.

Missing sync_file_range and syncfs System
Calls: We registered and defined
SYS_sync_file_range (84) and
SYS_syncfs (267) in abi.h and dispatch.tbl,
and implemented them in syscall.c as
aliases of sc_fsync_common.
@doanbaotrung

Copy link
Copy Markdown
Collaborator Author

Updated

@jserv jserv merged commit 884850f into sysprog21:main Jun 30, 2026
4 checks passed
@jserv

jserv commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Thank @doanbaotrung for contributing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants